/*
Copyright (c) 2025 HeZongLun
NahidaProject is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan
PSL v2.
You may obtain a copy of Mulan PSL v2 at:
		 http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
*/

#pragma once

#ifdef _WIN32
#define ExportSymbol  __declspec(dllexport)
#else
#define ExportSymbol
#endif

#include <iostream>
#include <utility>
#include <random>
#include <ranges>
#include <concepts>
#include <array>

template<typename TRange> concept ArithmeticRange = std::ranges::range<TRange> && std::is_arithmetic_v<std::ranges::range_value_t<TRange>>;

namespace  NahidaProject {
	class ExportSymbol RandomGenerator {
	private:
		using Engine = std::mt19937;
		using IntDistribution = std::uniform_int_distribution<>;
		using RealDistribution = std::uniform_real_distribution<>;

		Engine& GetEngine() {
			thread_local Engine engine;
			return engine;
		}

	public:
		RandomGenerator() noexcept;
		RandomGenerator(unsigned int seed) noexcept;
		unsigned int Next(unsigned int max) noexcept;
		int NextInt(int min, int max) noexcept;
		double NextDouble(double min, double max) noexcept;
		double NextDouble() noexcept;

		template<ArithmeticRange TRange> void Fill(TRange&& range, int min, int max) noexcept {
			auto& engine = GetEngine();
			for (auto& item : range) {
				item = IntDistribution(min, max)(engine);
			}
		}

		template<ArithmeticRange TRange> void Fill(TRange&& range, double min, double max) noexcept {
			auto& engine = GetEngine();
			for (auto& item : range) {
				item = RealDistribution(min, max)(engine);
			}
		}

		template<ArithmeticRange TRange> void Fill(TRange&& range) noexcept {
			auto& engine = GetEngine();
			for (auto& item : range) {
				item = RealDistribution(0, 1)(engine);
			}
		}

		template<std::ranges::random_access_range TRange> void Shuffle(TRange&& range) noexcept {
			std::ranges::shuffle(std::forward<TRange>(range), GetEngine());
		}

		template<size_t Length, std::ranges::random_access_range TRange, typename T = std::ranges::range_value_t<TRange>> std::array<T, Length> GetItems(TRange&& choises) {
			const size_t length = std::ranges::size(choises);
			std::array<T, Length> array;

			if (length == 0){
				return array;
			}

			const size_t last = length - 1;
			for (size_t i = 0; i < Length; i++){
				array[i] = choises[Next(last)];
			}

			return array;
		}

		template<std::ranges::random_access_range TChoisesRange, std::ranges::random_access_range TDestinationRange> requires std::same_as<std::ranges::range_value_t<TChoisesRange>, std::ranges::range_value_t<TDestinationRange>> void GetItems(TChoisesRange&& choises, TDestinationRange& destination) {
			const size_t choisesLength = std::ranges::size(choises);
			const size_t destinationLength = std::ranges::size(destination);

			if (choisesLength == 0 || destinationLength == 0) {
				return;
			}

			const size_t last = choisesLength - 1;

			for (size_t i = 0; i < destinationLength; i++) {
				destination[i] = choises[Next(last)];
			}
		}

	};
}
